
import { TextWriter, JsonWriter, HtmlWriter } from '../file/FileWriter';
import { ILoggable, Log } from './Log';

/**
 * output type
 */
export enum OutputType {
    CONSOLE = 'console',
    TEXT = 'txt',
    JSON = 'json',
    HTML = 'html'
}

/**
 * output config
 */
export type OutputCfg = {
    desc: any;
    output: {
        dir: string;
        filename: string;
        extension: string;
    }
    node?:  {
        enableId: boolean,
        enableType: boolean,
        enableName: boolean,
        enableSelfSize: boolean,
        enableRetainedSize: boolean
    }
}

/**
 * output interface
 */
export interface IOutput {
    type: OutputType;
    output(data: any, cfg: OutputCfg): void;
}

/**
 * console data
 */
export class ConsoleData {
    table: boolean;
    data: any;

    constructor(table: boolean, data: any) {
        this.table = table;
        this.data = data;
    }
}

/**
 * console table data
 */
export class ConsoleTableData {
    title: string[];
    data: any[];

    constructor(title: string[], data: any[]) {
        this.title = title;
        this.data = data;
    }
}

/**
 * text data
 */
export class TextData {
    desc: any;
    data: any;

    constructor(desc: any, data: any) {
        this.desc = desc;
        this.data = data;
    }
}

/**
 * json data
 */
export class JsonData {
    desc: any;
    data: any;

    constructor(desc: any, data: any) {
        this.desc = desc;
        this.data = data;
    }
}

/**
 * html data
 */
export class HtmlData {
    desc: any;
    data: any;
    template: string;
    delimiter: string;

    constructor(desc: any, data: any, template?: string) {
        this.desc = desc;
        this.data = data;
        this.template = template || '';
        this.delimiter = 'DATA=';
    }
}

/**
 * base output
 */
abstract class BaseOutput implements IOutput, ILoggable {
    public readonly type: OutputType;
    public readonly DOMAIN: string = 'meminsight';
    public readonly TAG: string;

    constructor(type: OutputType) {
        this.type = type;
        this.TAG = type.toString();
    }

    public output(data: any, cfg?: OutputCfg): void {}

    protected getFilePathByCfg(cfg?: OutputCfg) : string {
    if (cfg && cfg.output.filename != '') {
        return `${cfg.output.dir}/${cfg.output.filename}.${cfg.output.extension}`;
    } else {
        let dir = './output';
        let filename = `output_${Date.now()}`;
        let extension = this.type.toString();
        return `${dir}/${filename}.${extension}`;
    }
}
}

/**
 * console output
 */
export class ConsoleOutput extends BaseOutput {
    constructor() {
        super(OutputType.CONSOLE);
    }

    public output(data: any, cfg?: OutputCfg): void {
        const consoleData = data as ConsoleData;
        if (consoleData) {
            if (cfg?.desc) {
                console.log(cfg?.desc);
            }
            if (consoleData.table) {
                const tableData = consoleData.data as ConsoleTableData;
                console.table(tableData.data, tableData.title);
            } else {
                console.log(consoleData.data);
            }
        } else {
            if (typeof data === 'string') {
                 console.log(data);
            } else {
                Log.errorX(this, 'No data to output');
            }
        }
    }
}

/**
 * text output
 */
export class TextOutput extends BaseOutput {
    constructor() {
        super(OutputType.TEXT);
    }

    public output(data: any, cfg?: OutputCfg): void {
        let filePath = this.getFilePathByCfg(cfg);
        let textData = data as TextData;
        if (textData) {
            const tmpData = {
                'meta': cfg?.desc,
                'data': textData,
            }
            TextWriter.instance().writeToFile(filePath, JSON.stringify(tmpData), 'utf-8');
        } else {
            if (typeof data === 'string') {
                TextWriter.instance().writeToFile(filePath, data, 'utf-8');
            } else {
                Log.errorX(this, 'No data to output');
            }
        }
    }
}

/**
 * json output
 */
export class JsonOutput extends BaseOutput {
    constructor() {
        super(OutputType.JSON);
    }

    public output(data: any, cfg?: OutputCfg): void {
        let filePath = this.getFilePathByCfg(cfg);
        let jsonData = data as JsonData;
        if (jsonData) {
            const tmpData = {
                'meta': jsonData.desc,
                'data': jsonData.data
            }
            JsonWriter.instance().writeToFile(filePath, tmpData, 'utf-8');
        } else {
            if (typeof data === 'string') {
                JsonWriter.instance().writeToFile(filePath, data, 'utf-8');
            } else {
                Log.errorX(this, 'No data to output');
            }
        }
    }
}

/**
 * html output
 */
export class HtmlOutput extends BaseOutput {
    constructor() {
        super(OutputType.HTML);
    }

    public output(data: any, cfg?: OutputCfg): void {
       let filePath = this.getFilePathByCfg(cfg);
        let htmlData = data as HtmlData;
        if (htmlData) {
            const tmpData = {
                'meta': cfg?.desc,
                'data': htmlData,
            }
            HtmlWriter.instance().writeToFile(filePath, JSON.stringify(tmpData), 'utf-8');
        } else {
            if (typeof data === 'string') {
                HtmlWriter.instance().writeToFile(filePath, data, 'utf-8');
            } else {
                Log.errorX(this, 'No data to output');
            }
        }
    }
}

/**
 * output factory
 */
export class OutputFactory {
    private static _defaultOutput: IOutput = new ConsoleOutput();
    private static OUTPUT_MAP: Map<string, IOutput> = new Map(
        [
            [OutputType.CONSOLE.toString(), OutputFactory._defaultOutput],
            [OutputType.HTML.toString(), new HtmlOutput()],
            [OutputType.JSON.toString(), new JsonOutput()],
            [OutputType.TEXT.toString(), new TextOutput()],
        ]
    );
    

    public static getOutput(type: string): IOutput {
        let output = OutputFactory.OUTPUT_MAP.get(type);
        if (!output) {
            return OutputFactory._defaultOutput;
        }
        return output;
    }
}